﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ADONETDataServicesExtension.Utils
{
    internal static class ReflectionHelper
    {
        private static Dictionary<string, Type> _types = new Dictionary<string, Type>();

        internal static readonly Assembly FrameworkAssembly = typeof(System.Data.Services.DataService<>).Assembly;

        public static Type GetFrameworkType(string typeName)
        {
            var value = _types.Where(kvp => kvp.Key == typeName).Select(kvp => kvp.Value).FirstOrDefault();
            if (value == null)
            {
                value = FrameworkAssembly.GetType(typeName);
                _types.Add(typeName, value);
            }
            return value;
        }

        private static MethodInfo GetMethod(MethodInfo[] methods, string methodName, params object[] parameters)
        {
            var possibleMethods = new List<MethodInfo>();
            foreach (var m in methods)
                if (m.Name == methodName)
                {
                    ParameterInfo[] methodParameters = m.GetParameters();
                    int methodParametersLength = methodParameters.Length;
                    if (parameters.Length != methodParametersLength)
                        continue;
                    bool ok = true;
                    for (int i = 0; i < methodParametersLength; i++)
                    {
                        var methodParameterType = methodParameters[i].ParameterType;
                        if (methodParameterType.IsByRef)
                            methodParameterType = methodParameterType.Assembly.GetType(methodParameterType.FullName.Replace("&", ""));
                        var parameter = parameters[i];
                        if (parameter != null)
                        {
                            var parameterType = parameter.GetType();
                            if (methodParameterType.IsAssignableFrom(parameterType))
                                continue;
                            if (methodParameterType.IsEnum && parameterType == typeof(int))
                                continue;
                        }
                        else
                        {
                            if (methodParameterType.IsNullable())
                                continue;
                        }
                        ok = false;
                        break;
                    }
                    if (ok)
                        possibleMethods.Add(m);
                }

            int possibleMethodsCount = possibleMethods.Count;
            switch (possibleMethodsCount)
            {
                case 0:
                    throw new InvalidOperationException();
                case 1:
                    return possibleMethods[0];
                default:
                    for (int indexMethod = 1; indexMethod < possibleMethodsCount; indexMethod++)
                    {
                        var possibleMethodI = possibleMethods[0];
                        var possibleMethodI1 = possibleMethods[1];
                        if (possibleMethodI.DeclaringType == possibleMethodI1.DeclaringType)
                        {
                            ParameterInfo[] methodIParameters = possibleMethodI.GetParameters();
                            ParameterInfo[] methodI1Parameters = possibleMethodI1.GetParameters();
                            int methodParametersLength = methodIParameters.Length;
                            for (int i = 0; i < methodParametersLength; i++)
                            {
                                var possibleMethodIPar = methodIParameters[i];
                                var possibleMethodI1Par = methodI1Parameters[i];
                                if (possibleMethodIPar.ParameterType.IsAssignableFrom(possibleMethodI1Par.ParameterType))
                                {
                                    possibleMethods.RemoveAt(0);
                                    break;
                                }
                                else if (possibleMethodI1Par.ParameterType.IsAssignableFrom(possibleMethodIPar.ParameterType))
                                {
                                    possibleMethods.RemoveAt(1);
                                    break;
                                }
                            }
                        }
                        else if (possibleMethodI.DeclaringType.IsAssignableFrom(possibleMethodI1.DeclaringType))
                            possibleMethods.RemoveAt(0);
                        else if (possibleMethodI1.DeclaringType.IsAssignableFrom(possibleMethodI.DeclaringType))
                            possibleMethods.RemoveAt(1);
                    }
                    return possibleMethods[0];
            }
        }

        public static object InvokeMethod(string typeName, string methodName, object instance, params object[] parameters)
        {
            return InvokeMethod(GetFrameworkType(typeName), methodName, instance, parameters);
        }
        public static object InvokeMethod(Type type, string methodName, object instance, params object[] parameters)
        {
            return InvokeMethod(GetMethod(type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public), methodName, parameters), instance, parameters);
        }
        public static object InvokeMethod(MethodInfo method, object instance, params object[] parameters)
        {
            return method.Invoke(instance, parameters);
        }
        public static T InvokeMethod<T>(string typeName, string methodName, object instance, params object[] parameters)
        {
            return (T)InvokeMethod(typeName, methodName, instance, parameters);
        }
        public static T InvokeMethod<T>(Type type, string methodName, object instance, params object[] parameters)
        {
            return (T)InvokeMethod(type, methodName, instance, parameters);
        }
        public static T InvokeMethod<T>(MethodInfo method, object instance, params object[] parameters)
        {
            return (T)method.Invoke(instance, parameters);
        }
        public static void InvokeVoidMethod(string typeName, string methodName, object instance, params object[] parameters)
        {
            InvokeVoidMethod(GetFrameworkType(typeName), methodName, instance, parameters);
        }
        public static void InvokeVoidMethod(Type type, string methodName, object instance, params object[] parameters)
        {
            InvokeMethod(GetMethod(type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public), methodName, parameters), instance, parameters);
        }

        public static object GetProperty(string typeName, string propertyName, object instance)
        {
            return GetProperty(GetFrameworkType(typeName), propertyName, instance);
        }
        public static object GetProperty(Type type, string propertyName, object instance)
        {
            return GetInstancePropertyInfo(type, propertyName).GetValue(instance, null);
        }
        public static T GetProperty<T>(string typeName, string propertyName, object instance)
        {
            return (T)GetProperty(typeName, propertyName, instance);
        }
        public static T GetProperty<T>(Type type, string propertyName, object instance)
        {
            return (T)GetProperty(type, propertyName, instance);
        }
        public static PropertyInfo GetInstancePropertyInfo(Type type, string propertyName)
        {
            return type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        }

        public static void SetProperty(string typeName, string propertyName, object instance, object value)
        {
            SetProperty(GetFrameworkType(typeName), propertyName, instance, value);
        }
        public static void SetProperty(Type type, string propertyName, object instance, object value)
        {
            type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).SetValue(instance, value, null);
        }

        public static object GetField(string typeName, string fieldName, object instance)
        {
            return GetField(GetFrameworkType(typeName), fieldName, instance);
        }
        public static object GetField(Type type, string fieldName, object instance)
        {
            return type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).GetValue(instance);
        }
        public static T GetField<T>(string typeName, string fieldName, object instance)
        {
            return (T)GetField(typeName, fieldName, instance);
        }
        public static T GetField<T>(Type type, string fieldName, object instance)
        {
            return (T)GetField(type, fieldName, instance);
        }

        public static void SetField(string typeName, string fieldName, object instance, object value)
        {
            SetField(GetFrameworkType(typeName), fieldName, instance, value);
        }
        public static void SetField(Type type, string fieldName, object instance, object value)
        {
            type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).SetValue(instance, value);
        }

        public static object InvokeStaticMethod(MethodInfo method, params object[] parameters)
        {
            return method.Invoke(null, parameters);
        }
        public static object InvokeStaticMethod(string typeName, string methodName, params object[] parameters)
        {
            return InvokeStaticMethod(GetFrameworkType(typeName), methodName, parameters);
        }
        public static object InvokeStaticMethod(Type type, string methodName, params object[] parameters)
        {
            return InvokeStaticMethod(GetMethod(type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public), methodName, parameters), parameters);
        }
        public static T InvokeStaticMethod<T>(string typeName, string methodName, params object[] parameters)
        {
            return (T)InvokeStaticMethod(typeName, methodName, parameters);
        }
        public static T InvokeStaticMethod<T>(Type type, string methodName, params object[] parameters)
        {
            return (T)InvokeStaticMethod(type, methodName, parameters);
        }
        public static void InvokeStaticVoidMethod(string typeName, string methodName, params object[] parameters)
        {
            InvokeStaticVoidMethod(GetFrameworkType(typeName), methodName, parameters);
        }
        public static void InvokeStaticVoidMethod(Type type, string methodName, params object[] parameters)
        {
            InvokeStaticMethod(GetMethod(type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public), methodName, parameters), parameters);
        }

        public static object GetStaticProperty(string typeName, string propertyName)
        {
            return GetStaticProperty(GetFrameworkType(typeName), propertyName);
        }
        public static object GetStaticProperty(Type type, string propertyName)
        {
            return type.GetProperty(propertyName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).GetValue(null, null);
        }
        public static T GetStaticProperty<T>(string typeName, string propertyName)
        {
            return (T)GetStaticProperty(typeName, propertyName);
        }
        public static T GetStaticProperty<T>(Type type, string propertyName)
        {
            return (T)GetStaticProperty(type, propertyName);
        }

        public static object GetStaticField(string typeName, string fieldName)
        {
            return GetStaticField(GetFrameworkType(typeName), fieldName);
        }
        public static object GetStaticField(Type type, string fieldName)
        {
            return type.GetField(fieldName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).GetValue(null);
        }
        public static T GetStaticField<T>(string typeName, string fieldName)
        {
            return (T)GetStaticField(typeName, fieldName);
        }
        public static T GetStaticField<T>(Type type, string fieldName)
        {
            return (T)GetStaticField(type, fieldName);
        }

        public static void SetStaticField(string typeName, string fieldName, object value)
        {
            SetStaticField(GetFrameworkType(typeName), fieldName, value);
        }
        public static void SetStaticField(Type type, string fieldName, object value)
        {
            type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).SetValue(null, value);
        }

        public static object CreateInstance(string typeName, params object[] parameters)
        {
            return CreateInstance(ReflectionHelper.GetFrameworkType(typeName), parameters);
        }
        public static object CreateInstance(Type type, params object[] parameters)
        {
            return Activator.CreateInstance(type, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, parameters, null, null);
        }
        public static T CreateInstance<T>(string typeName, params object[] parameters)
        {
            return (T)CreateInstance(typeName, parameters);
        }
        public static T CreateInstance<T>(Type type, params object[] parameters)
        {
            return (T)CreateInstance(type, parameters);
        }

        public static K Convert<T, K>(T framewokrValue, Func<T, K> convert)
            where T : class
            where K : class
        {
            if (convert == null)
                throw new ArgumentNullException();
            if (framewokrValue == null)
                return null;
            return convert(framewokrValue);
        }
    }
}
